package model;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Paint;
import java.awt.Shape;
import java.awt.geom.Ellipse2D;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Map;
import java.util.Random;

import optimizers.commons.evaluator.TimeSeriesCalculationService;
import optimizers.ganeat.Connection;
import optimizers.ganeat.Individual;
import optimizers.ganeat.Node;
import optimizers.ganeat.math.ConstantsMath;
import optimizers.ganeat.oscillator.robustfaststrong.ConstantsRobustFSO;

import org.apache.commons.collections15.Transformer;

import common.Static;

import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.graph.util.EdgeType;
import edu.uci.ics.jung.visualization.BasicVisualizationServer;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
import edu.uci.ics.jung.visualization.control.ScalingControl;
import edu.uci.ics.jung.visualization.renderers.Renderer.VertexLabel.Position;
import util.MyUtil;
import visualization.MyVertexLebeller;
import visualization.OligoGraph;
import visualization.OligoISOMLayout;
import visualization.OligoRenderer;
import visualization.PA2EdgeArrowTransformer;

public class OligoSystemGeneral implements Serializable {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	public int nSimpleSequences;
	public double[][][] templates;
	public double[][] seqK;
	public double[][] inhK;
	public double[][] seqConcentration;
	public boolean[] sequenceProtected;
	public Enzyme exo;
	public Enzyme nick;
	public Enzyme poly;
	public double[] reporterConcentration;
	public String[] seqString;

	// /////DYNAMIC PRINTING//////////
	public double[][] TimeSerie;
	public int t = 0;
	public int t2 = 0;
	public Individual indiv;
	public float Fmax;
	// ///////////////////////////////

	public Map<Integer, String> intToName;
	public Map<String, Integer> nameToInt;

	public OligoSystemGeneral(int nSimpleSequences, double[][][] templates,
			double[][] seqK, double[][] inhK, double[][] seqConcentration,
			double exo, Map<Integer, String> intToName,
			Map<String, Integer> nameToInt) {
		this.nSimpleSequences = nSimpleSequences;
		this.templates = templates;
		this.seqK = seqK;
		this.inhK = inhK;
		this.seqConcentration = seqConcentration;
		this.exo = new Enzyme("exo", exo, model.Constants.exoKmSimple,
				model.Constants.exoKmInhib);
		this.nick = new Enzyme("nick", model.Constants.nickVm
				/ Constants.nickKm, Constants.nickKm);
		this.poly = new Enzyme("poly", Constants.polVm / Constants.polKm,
				Constants.polKm, Constants.polKmBoth);
		this.nameToInt = nameToInt;
		this.intToName = intToName;
	}

	public OligoSystemGeneral(int nSimpleSequences, double[][][] templates,
			double[][] seqK, double[][] inhK, double[][] seqConcentration,
			Map<Integer, String> intToName, Map<String, Integer> nameToInt) {
		this.nSimpleSequences = nSimpleSequences;
		this.templates = templates;
		this.seqK = seqK;
		this.inhK = inhK;
		this.seqConcentration = seqConcentration;
		this.exo = new Enzyme("exo", Constants.exoVm / Constants.exoKmSimple,
				model.Constants.exoKmSimple, model.Constants.exoKmInhib);
		this.nick = new Enzyme("nick", model.Constants.nickVm
				/ Constants.nickKm, Constants.nickKm);
		this.poly = new Enzyme("poly", Constants.polVm / Constants.polKm,
				Constants.polKm, Constants.polKmBoth);
		this.nameToInt = nameToInt;
		this.intToName = intToName;
	}

	public OligoSystemGeneral(int nSimpleSequences, double[][][] templates,
			double[][] seqK, double[][] inhK, double[][] seqConcentration,
			Enzyme exo, Enzyme nick, Enzyme poly,
			Map<Integer, String> intToName, Map<String, Integer> nameToInt,
			String[] seqStr) {
		this.nSimpleSequences = nSimpleSequences;
		this.templates = templates;
		this.seqK = seqK;
		this.inhK = inhK;
		this.seqConcentration = seqConcentration;
		this.exo = exo;
		this.nick = nick;
		this.poly = poly;
		this.nameToInt = nameToInt;
		this.intToName = intToName;
		this.seqString = seqStr;
	}

	public OligoSystemGeneral(int nSimpleSequences, double[][][] templates,
			double[][] seqK, double[][] inhK, double[][] seqConcentration,
			Enzyme exo, Enzyme nick, Enzyme poly,
			Map<Integer, String> intToName, Map<String, Integer> nameToInt,
			boolean[] sequenceProtected, double[] reporterConcentration,
			String[] seqString) {
		this(nSimpleSequences, templates, seqK, inhK, seqConcentration, exo,
				nick, poly, intToName, nameToInt, seqString);
		this.sequenceProtected = sequenceProtected;
		this.reporterConcentration = reporterConcentration;
	}

	public ArrayList<Double> getSeqK() {
		ArrayList<Double> result = new ArrayList<Double>();
		for (int i = 0; i < nSimpleSequences + 1; i++) {
			for (int j = 0; j < nSimpleSequences; j++) {
				if (seqK[i][j] != 0) {
					result.add(seqK[i][j]);
				}
			}
		}
		return result;
	}

	public OligoSystemGeneral getNoisyVersion() {
		double[][][] template = new double[nSimpleSequences + 1][nSimpleSequences][nSimpleSequences];
		double[][] seqK = new double[nSimpleSequences + 1][nSimpleSequences];
		double[][] seqC = new double[nSimpleSequences + 1][nSimpleSequences];
		double[][] inhK = new double[nSimpleSequences + 1][nSimpleSequences];

		Random rand = new Random();

		// Template
		for (int i = 0; i < nSimpleSequences + 1; i++) {
			for (int j = 0; j < nSimpleSequences; j++) {
				for (int k = 0; k < nSimpleSequences; k++) {
					template[i][j][k] = this.templates[i][j][k];
				}
			}
		}

		// SeqK
		for (int i = 0; i < nSimpleSequences + 1; i++) {
			for (int j = 0; j < nSimpleSequences; j++) {
				seqK[i][j] = MyUtil.multipliactiveGaussianTransform(rand,
						this.seqK[i][j]);
			}
		}

		// Sequence concentration
		for (int i = 0; i < nSimpleSequences + 1; i++) {
			for (int j = 0; j < nSimpleSequences; j++) {
				seqC[i][j] = this.seqConcentration[i][j];
			}
		}

		// inhK
		// for (int i = 0; i < nSimpleSequences + 1; i++) {
		// for (int j = 0; j < nSimpleSequences; j++) {
		// inhK[i][j] = MyUtil.multipliactiveGaussianTransform(rand,
		// this.inhK[i][j]);
		// }
		// }

		for (int i = 0; i < nSimpleSequences + 1; i++) {
			for (int j = 0; j < nSimpleSequences; j++) {
				inhK[i][j] = seqK[i][j] * model.Constants.ratioInhibition;
			}
		}
		OligoSystemGeneral oligoSystem = new OligoSystemGeneral(
				nSimpleSequences, template, seqK, inhK, seqC, exo, nick, poly,
				intToName, nameToInt, seqString);
		return oligoSystem;
	}

	public double[][] getTimeSeriesComplexModel() {
		OligoSystemComplex oligoModel = new OligoSystemComplex(
				nSimpleSequences, templates, seqK, inhK, seqConcentration, exo,
				poly, nick, sequenceProtected, reporterConcentration, seqString);
		return oligoModel.calculateTimeSeries(false);
	}

	public double[][] getTimeSeriesWithInputComplexModel(Input[] inputs) {
		OligoSystemComplex oligoModel = new OligoSystemComplex(
				nSimpleSequences, templates, seqK, inhK, seqConcentration, exo,
				poly, nick, sequenceProtected, reporterConcentration, seqString);
		oligoModel.setInput(inputs);
		double[][] timeSeriesWithInput = oligoModel.calculateTimeSeries(false);
		return timeSeriesWithInput;
	}

	public double[][] getTimeSeriesComplexModelTillStable() {
		OligoSystemComplex oligoModel = new OligoSystemComplex(
				nSimpleSequences, templates, seqK, inhK, seqConcentration, exo,
				poly, nick, sequenceProtected, reporterConcentration, seqString);
		return oligoModel.calculateTimeSeries(true);
	}

	public double[][][] getTimeSeriesComplexModelTillStableWithDifferentInputSet(
			ArrayList<double[]> sets) {
		TimeSeriesCalculationService service = new TimeSeriesCalculationService();
		OligoSystemComplex[] oligoSystems = new OligoSystemComplex[sets.size()];
		for (int j = 0; j < sets.size(); j++) {
			double[] inputs = sets.get(j);
			double[][] seqConcentration = new double[this.seqConcentration.length][this.seqConcentration[0].length];
			MyUtil.multiArrayCopy(this.seqConcentration, seqConcentration);
			for (int i = 0; i < ConstantsMath.nInput; i++) {
				seqConcentration[0][i] = inputs[i];
			}
			oligoSystems[j] = new OligoSystemComplex(nSimpleSequences,
					templates, seqK, inhK, seqConcentration, exo, poly, nick,
					sequenceProtected, reporterConcentration, seqString);
		}
		return service.calculateTimeSeries(oligoSystems);
	}

	// TODO: as of now, oligosystemsimple ignores exo, poly and nick...
	public double[][] getTimeSeriesSimpleModel() {
		OligoSystemSimple oligoModel = new OligoSystemSimple(nSimpleSequences,
				templates, seqK, inhK, seqConcentration, exo.activity);
		oligoModel.exo = exo;
		oligoModel.poly = poly;
		oligoModel.nick = nick;
		return oligoModel.calculateTimeSeries();
	}

	public double[][] getTimeSeriesWithInputSimpleModel(Input[] inputs) {
		OligoSystemSimple oligoModel = new OligoSystemSimple(nSimpleSequences,
				templates, seqK, inhK, seqConcentration, exo.activity);
		oligoModel.setInput(inputs);
		oligoModel.exo = exo;
		oligoModel.poly = poly;
		oligoModel.nick = nick;
		double[][] timeSeriesWithInput = oligoModel.calculateTimeSeries();
		return timeSeriesWithInput;
	}

	public String getName(int i) {
		return intToName.get(i);
	}

	public int getId(String name) {
		return nameToInt.get(name);
	}

	public BasicVisualizationServer<String, String> getVisualization(int x,
			int y, Map<String, java.awt.geom.Point2D> locs) {
		OligoGraph<String, String> g2 = new OligoGraph<String, String>();
		// add all simple sequences to the graph
		for (int i = 0; i < nSimpleSequences; i++) {
			g2.addVertex(getName(i));
			g2.addVertexK(getName(i), seqK[0][i]);
		}
		// add all inhibition sequences to the graph
		for (int i = 0; i < nSimpleSequences; i++) {
			for (int j = 0; j < nSimpleSequences; j++) {
				if (inhK[i + 1][j] > 0) {
					g2.addVertex("I" + getName(i) + getName(j));
					g2.addVertexK("I" + getName(i) + getName(j), seqK[i + 1][j]);
				}
			}
		}
		// add activations to the graph
		for (int i = 0; i < nSimpleSequences; i++) {
			for (int j = 0; j < nSimpleSequences; j++) {
				if (templates[0][i][j] > 0) {
					g2.addEdge(getName(i) + getName(j), getName(i), getName(j),
							EdgeType.DIRECTED);
					g2.addEdgeConcentration(getName(i) + getName(j),
							templates[0][i][j]);
				}
			}
		}
		// add inhibitions to the graph
		for (int i = 0; i < nSimpleSequences; i++) {
			for (int j = 0; j < nSimpleSequences; j++) {
				for (int k = 0; k < nSimpleSequences; k++) {
					if (templates[i + 1][j][k] > 0) {
						String inhibitedActivation = getName(j) + getName(k);
						g2.addEdge(getName(i) + "I" + inhibitedActivation,
								getName(i), "I" + inhibitedActivation,
								EdgeType.DIRECTED);
						g2.addEdgeConcentration(getName(i) + "I"
								+ inhibitedActivation, templates[i + 1][j][k]);
						g2.addInhibition("I" + inhibitedActivation, "I"
								+ inhibitedActivation, inhibitedActivation);
					}
				}
			}
		}

		Layout<String, String> layout = new OligoISOMLayout<String, String>(g2,
				locs);
		layout.setSize(new Dimension(x, y));
		VisualizationViewer<String, String> vv = new VisualizationViewer<String, String>(
				layout);
		vv.setPreferredSize(new Dimension(x, y));
		vv.setBackground(Color.WHITE);
		// Setup up a new vertex to paint transformer...
		Transformer<String, Paint> vertexPaint = new Transformer<String, Paint>() {
			public Paint transform(String i) {
				return Color.GREEN;
			}
		};
		vv.setRenderer(new OligoRenderer<String, String>());
		vv.getRenderContext().setVertexFillPaintTransformer(vertexPaint);
		vv.getRenderContext().setEdgeArrowTransformer(
				new PA2EdgeArrowTransformer<String, String>());
		vv.getRenderContext().setVertexLabelTransformer(
				new MyVertexLebeller<String>());

		// /////////////////////////////////DYNAMIC
		// PRINTING///////////////////////////////////////

		if (indiv != null) {

			// Edges shape
			// Transformer<Context<Graph<String,String>,String>,Shape> edgeShape
			// = new Transformer<Context<Graph<String,String>,String>,Shape>() {
			// @Override
			// public Shape transform(Context<Graph<String, String>, String> i)
			// {
			// for(Connection conn : indiv.connectionGenes){
			// if( i.equals(conn.from.name + conn.from.name) )
			// return (new CubicCurve2D.Double(1, 1, 1, 1, 5, 5, 10, 10));
			// }
			// return (new Line2D.Double(0,0,1,1));
			// }
			// };
			// vv.getRenderContext().setEdgeShapeTransformer(edgeShape);

			Fmax = 0;
			for (Connection conn : indiv.connectionGenes) {
				String From = conn.from.name;
				String To = conn.to.name;
				for (int T = 75; T < 500; T++) {
					double FInh = 0;
					if (indiv.getNode("I" + From + To) != null) {
						Node Inh = indiv.getNode("I" + From + To);
						FInh = TimeSerie[nameToInt.get(Inh.name)][t]
								* Inh.parameter;
					}
					double F = conn.parameter
							* TimeSerie[nameToInt.get(conn.from.name)][T];
					F = F - FInh > 0 ? F - FInh : 0;
					if (Fmax < F)
						Fmax = (float) F;
				}
			}
			// System.err.println("Fmax : " + Fmax);

			// Edges color
			Transformer<String, Paint> edgePaint = new Transformer<String, Paint>() {
				@Override
				public Paint transform(String i) {
					String From = "";
					String To = "";
					Connection con = null;
					double FInh = 0;
					boolean Cut = false;
					for (Connection conn : indiv.connectionGenes) {
						if (i.equals(conn.from.name + conn.to.name)) {
							From = conn.from.name;
							To = conn.to.name;
							con = conn;
							if (indiv.getNode("I" + From + To) != null) {
								Node Inh = indiv.getNode("I" + From + To);
								FInh = TimeSerie[nameToInt.get(Inh.name)][t]
										* Inh.parameter;
							}
						}
					}

					float[] colorVect = { 0, 0, 0 };
					double F = TimeSerie[nameToInt.get(From)][t]
							* con.parameter;
					F = F - FInh > 0 ? F - FInh : 0;
					colorVect[0] = (float) (Math.pow(F * 10, 0.05) / Math.pow(
							Fmax * 10, 0.05)) * 255;
					colorVect[2] = 255 - (float) (Math.pow(F * 10, 0.05) / Math
							.pow(Fmax * 10, 0.05)) * 255;

					if (colorVect[0] > 255)
						colorVect[0] = 255;
					if (colorVect[2] < 0)
						colorVect[2] = 0;
					if (colorVect[0] == 0)
						Cut = true;
					// System.err.println("RGB pour " + i + " :    " +
					// (int)colorVect[0] + "," + (int)colorVect[1] + "," +
					// (int)colorVect[2] + ",   t = " + t);
					colorVect = Color.RGBtoHSB((int) colorVect[0],
							(int) colorVect[1], (int) colorVect[2], colorVect);
					Paint color = Color.getHSBColor(colorVect[0], colorVect[1],
							colorVect[2]);
					if (Cut)
						color = Color.GRAY;
					return color;
				}
			};
			vv.getRenderContext().setEdgeDrawPaintTransformer(edgePaint);
			vv.getRenderContext().setArrowDrawPaintTransformer(edgePaint);
			vv.getRenderContext().setArrowFillPaintTransformer(edgePaint);

			// Vertexes radius
			Transformer<String, Shape> vertexShape = new Transformer<String, Shape>() {
				double r = 10;
				int C = 0;
				double A = 250 * 250;
				double K = Math.pow(1.5 * 1.5 * A / nameToInt.size(), 0.5);
				ArrayList<Double> r0 = new ArrayList<Double>();

				// ArrayList<Double> Moy = new ArrayList<Double>();

				@Override
				public Shape transform(String i) {
					// System.err.println( i + " : " +
					// fight[nameToInt.get(i)][250] );
					if (t2 == 0) {
						for (int j = 0; j < nameToInt.size(); j++) {
							r0.add(10.0);
							// double M = 0.0;
							// if(fight != null){
							// for(int t1 = 0; t1 < 500; t1++){
							// M += fight[j][t1];
							// }
							// M = M / 500;
							// }
							// Moy.add(M);
						}
						t2 = 1;
					}
					r = r0.get(nameToInt.get(i));
					if (TimeSerie != null) {
						r = 8 + Math.log(1 + 30
								* TimeSerie[nameToInt.get(i)][t] / K) * 5;
						C++;
						t2 = 1;
						if (C == nameToInt.size()) {
							C = 0;
							t++;
						}
					}
					r0.set(nameToInt.get(i), r);
					if (t > 500)
						t = 0;

					Shape style = new Ellipse2D.Double(-r, -r, 2 * r, 2 * r);

					return (style);
				}
			};
			vv.getRenderContext().setVertexShapeTransformer(vertexShape);
		}

		// //////////////////////////////////////////////////////////////////////////////

		// vv.getRenderContext().setEdgeLabelTransformer(new
		// ToStringLabeller());
		vv.getRenderer().getVertexLabelRenderer().setPosition(Position.CNTR);
		DefaultModalGraphMouse<String, String> gm = new DefaultModalGraphMouse<String, String>();
		gm.setMode(ModalGraphMouse.Mode.PICKING);
		vv.setGraphMouse(gm);
		ScalingControl scaler = new CrossoverScalingControl();
		scaler.scale(vv, (float) x / 250, vv.getCenter());
		return vv;
	}

	public BasicVisualizationServer<String, String> getVisualization(int x,
			int y) {
		return getVisualization(x, y, null);
	}
}
